'''
KML Geometry Classes
Created on Sep 28, 2010

@author: morgan
'''
# from __xml__ import createElement
from __string__ import T
from base import KMLObject, Coordinate
from enum import altitudeMode, extrude, tessellate
from data import coordinates, Location, Orientation, Link, Scale, ResourceMap

__all__ = ['Geometry', 'Vector', 'Line', 'LinearRing', 'LineString', 'Point', 
           'MultiGeometry', 'Model']

class Geometry(KMLObject):
    """ 
    Base class for all geometry objects 
    """
    def __init__(self, id=""):
        KMLObject.__init__(self, id)
        
class Vector(Geometry):
    """
    Base class for all geometry except MultiGeometry and Model.  This is not a 
    true KML abstract class.
    """
    def __init__(self, id="", extrude=False, altitudeMode='absolute',
                 coordinates=[]):
        KMLObject.__init__(self, id)
        self.extrude = extrude
        self.altitudeMode = altitudeMode
        if coordinates != None:
            # This is done so that kml.Point doesn't trigger a call to our 
            # self.coordinates property.  There's probably a better way to do
            # this 
            self.coordinates = coordinates
    
    def __repr__(self):
        d=dict(id=self.id, module=self.__module__, cls=self.__class__.__name__,
               extrude=self.extrude, altitudeMode=self.altitudeMode,
               coordinates=repr(coordinates(self.coordinates)))
        return T("$module.$cls('$id', extrude=$extrude, "+\
                 "altitudeMode=$altitudeMode, coordinates=$coordinates)").\
                    substitute(d)
    @property
    def coordinates(self):
        return self._coordinates
    
    @coordinates.setter
    def coordinates(self, value):
        if type(value) == coordinates:
            self._coordinates = value.value
        elif type(value) in [list, tuple]:
            self._coordinates = value
        else:
            raise TypeError("coordinates must be a kml.coordinates object, list or tuple")
        
    @property
    def extrude(self):
        return self._extrude
        
    @extrude.setter
    def extrude(self, value):
        if type(value) != bool and not(isinstance(value, extrude)):
            raise TypeError("Expecting value to be boolean or an extrude"+
                            "class object")
        if type(value) == bool:
            self._extrude = value
        else:
            self._extrude = value.value
    
    @property
    def altitudeMode(self):
        return self._altMode
    
    @altitudeMode.setter 
    def altitudeMode(self, value):
        if type(value) != str and type(value) != altitudeMode:
            raise TypeError("Expecting value to be a str object  or a "+
                            "kml.altitudeMode instance")
        if type(value) == str:
            mode = altitudeMode()
            mode.actualMode = value
            self._altMode = str(mode)
        else:
            self._altMode = str(value) 
        
    def toXmlNode(self, kml):
        node = Geometry.toXmlNode(self, kml)
        if self.extrude:
            ext = extrude(self.extrude)
            node.appendChild(ext.toXmlNode(kml))
        if self.altitudeMode != altitudeMode.default:
            altMode = altitudeMode(self.altitudeMode)
            node.appendChild(altMode.toXmlNode(kml))
        if self.coordinates:
            coords = coordinates(self.coordinates)
            node.appendChild(coords.toXmlNode(kml))
        return node
                         
    def parseKMLNode(self, KMLNode):
        Geometry.parseKMLNode(self, KMLNode)
        if KMLNode.getElementsByTagName("altitudeMode") != []:
            altMode = altitudeMode()
            altMode.parseKMLNode(KMLNode.getElementsByTagName("altitudeMode")[0])
            self.altitudeMode = altMode
        if KMLNode.getElementsByTagName("extrude") != []:
            ext = extrude()
            ext.parseKMLNode(KMLNode.getElementsByTagName("extrude")[0])
            self.extrude = ext
        if KMLNode.getElementsByTagName("coordinates") != []:
            coords = coordinates()            
            coords.parseKMLNode(KMLNode.getElementsByTagName("coordinates")[0])
            self.coordinates = coords
    
class Line(Vector):
    """
    Base class for all line-based geometry classes. Do not instantiate.
    """
    def __init__(self, id="", tessellate=False, *args, **kwdargs):
        self.tessellate = tessellate
        Vector.__init__(self, id, *args, **kwdargs)
        
    @property
    def tessellate(self):
        return self._tessellate
    
    @tessellate.setter
    def tessellate(self, value):
        if type(value) != bool and type(value) != tessellate:
            raise TypeError("Expecting value to be True, False or a "+
                            "tessellate class object")
        if type(value) == bool:
            self._tessellate = value
        else:
            self._tessellate = value.value

    def toXmlNode(self,kml):
        lsNode = Vector.toXmlNode(self, kml)
        if self.tessellate:
            tess = tessellate(self.tessellate)
            lsNode.appendChild(tess.toXmlNode(kml))
        return lsNode
    
    def parseKMLNode(self, KMLNode):
        Vector.parseKMLNode(self, KMLNode)
        if KMLNode.getElementsByTagName("tessellate") != []:
            tess = tessellate()
            tess.parseKMLNode(KMLNode.getElementsByTagName("tessellate")[0])
            self.tessellate = tess
    
class LinearRing(Line):
    """ 
    Defines a closed line string, typically the outer boundary of a B{Polygon}. 
    Optionally, a B{LinearRing} can also be used as the inner boundary of a 
    B{Polygon} to create holes in the B{Polygon}. A B{Polygon} can contain 
    multiple B{LinearRing} elements used as inner boundaries.
    
    Note: In Google Earth, a B{Polygon} with an B{altitudeMode} of 
    I{clampToGround} follows lines of constant bearing; however, a 
    B{LinearRing} (by itself) with an B{altitudeMode} of I{clampToGround} 
    follows great circle lines.
    """
    def __init__(self, id="", *args, **kwdargs):
        self.__tagName__ = "LinearRing"
        Line.__init__(self, id, *args, **kwdargs)
        
    def toXmlNode(self,kml):
        lsNode = Line.toXmlNode(self, kml)
        if len(self.coordinates) < 4:
            raise Exception("Not enough points to define a linear ring")
        if self.coordinates[0] != self.coordinates[len(self.coordinates)-1]:
            raise Exception("Beginning point is not equal to the end point")        
        return lsNode           

class LineString(Line):
    """
    Defines a connected set of line segments. Use B{LineStyle} to specify the 
    color, color mode, and width of the line. When a B{LineString} is extruded, 
    the line is extended to the ground, forming a polygon that looks somewhat 
    like a wall or fence. For extruded B{LineStrings}, the line itself uses the 
    current BpLineStyle}, and the extrusion uses the current B{PolyStyle}. 
    See the KML Tutorial for examples of B{LineStrings} (or paths).
    """
    def __init__(self, id="", *args, **kwdargs):
        self.__tagName__ = "LineString"
        Line.__init__(self, id, *args, **kwdargs)      

class Point(Coordinate, Vector):
    """
    A geographic location defined by I{longitude}, I{latitude}, and (optional) 
    I{altitude}. When a Point is contained by a Placemark, the point itself 
    determines the position of the Placemark's name and icon. When a Point is
    extruded, it is connected to the ground with a line. This "tether" uses 
    the current B{LineStyle}.
    (a point is a vector with a single node! ;)
    """
    def __init__(self, id="", *args, **kwdargs):
        self.__coord_kwds__ = ["altitude", "longitude", "latitude", "coordinates"]
        self.__tagName__ = "Point"
        
        mykwds = {}
        for kwd in kwdargs.keys():
            if kwd in self.__coord_kwds__:
                mykwds[kwd] = kwdargs[kwd]
        Coordinate.__init__(self, *args, **mykwds)
        
        mykwds = {}
        for kwd in kwdargs.keys():
            if kwd not in self.__coord_kwds__:
                mykwds[kwd] = kwdargs[kwd]
        Vector.__init__(self, id, coordinates=None, **mykwds)

        #self.extrude = extrude()
        #self.altitudeMode = altitudeMode()
        
    def __repr__(self):
        d=dict(id=self.id, module=self.__module__, cls=self.__class__.__name__, 
               lon=self.longitude, lat=self.latitude, alt=self.altitude, 
               mode=self.altitudeMode, extrude=self.extrude)
        return T("$module.$cls(id='$id', $lon, $lat, $alt, "+\
                 "altitudeMode='$mode', extrude=$extrude)").substitute(d)
    
    def toXmlNode(self, kml):
        ptNode = Vector.toXmlNode(self, kml)
        return ptNode
        
    def parseKMLNode(self,KMLNode):
        Vector.parseKMLNode(self, KMLNode)
        if len(self.coordinates) == 0:
            raise Exception("coordinate xml information not present")

class MultiGeometry(Geometry):
    
    def __init__(self):
        self.__tagName__ = "MultiGeometry"
        Geometry.__init__(self, id)
        self._geoms = []

    def __len__(self):
        return len(self._geoms)

    def __getitem__(self,index):
        if isinstance(index, slice):
            return self._geoms[index.start:index.stop:index.step]
        else:
            return self._geoms[index]

    def append(self,geometry):
        if not any(isinstance(geometry, aType) for aType in [Point,LineString,LinearRing]):
            raise TypeError("Expecting geometry to be Point,LineString, or LinearRing")
        self._geoms.append(geometry)

    def remove(self,geomOrIndex):
        if not any(isinstance(geomOrIndex, aType) for aType in [Point,LineString,LinearRing]):
            self._geoms.remove(geomOrIndex)
        elif type(geomOrIndex) == int:
            if geomOrIndex >= len(self._geoms) or geomOrIndex < 0:
                raise ValueError("Index out of bounds")
            del self._geoms[geomOrIndex]
        else:
            raise TypeError("Expecting geomOrIndex to be a kml.Vector instance or an integral index")

    def parseKMLNode(self, KMLNode):
        Geometry.parseKMLNode(self, KMLNode)
        if KMLNode.getElementsByTagName("Point") != []:
            for pt in  KMLNode.getElementsByTagName("Point"):
                geom = Point()
                geom.parseKMLNode(pt)
                self.append(geom)
        elif KMLNode.getElementsByTagName("LineString") != []:
            for ls in KMLNode.getElementsByTagName("LineString"):
                geom = LineString()
                geom.parseKMLNode(ls)
                self.append(geom)
        elif KMLNode.getElementsByTagName("LinearRing") != []:
            for lr in KMLNode.getElementsByTagName("LinearRing"):
                geom = LineString()
                geom.parseKMLNode(lr)
                self.append(geom)
        
    def toXmlNode(self,kml):
        multiGeomNode = Geometry.toXmlNode(self, kml)
        for geom in self._geoms:
            multiGeomNode.appendChild(geom.toXmlNode(kml))
        return multiGeomNode
    
class Model(Geometry):
    def __init__(self, id="", altitudeMode=altitudeMode.default, location=None,
                 orientation=None, link=None, scale=None, resourceMap = None):
        self.__tagName__ = "Model"
        Geometry.__init__(self, id)
        self.altitudeMode = altitudeMode
        self.Location = location
        self.Orientation = orientation
        self.Scale = scale
        self.Link = link
        self.ResourceMap = resourceMap
        
    @property
    def altitudeMode(self):
        return self._altitudeMode
    
    @altitudeMode.setter
    def altitudeMode(self, value):
        if type(value) == str:
            self._altitudeMode = str(altitudeMode(value))
        elif type(value) == altitudeMode:
            self._altitudeMode = str(value)
        else:
            raise TypeError("altitudeMode must be a kml.altitudeMode object or string.")
    
    @property
    def Location(self):
        if hasattr(self, "_location"):
            return self._location
        else:
            return None
    
    @Location.setter
    def Location(self, location):
        if location is None:
            if hasattr(self, "_location"):
                del self._location
        elif type(location) == Location:
            self._location = location
        else:
            raise TypeError("Location must be kml.Location object or None.")
        
    @Location.deleter
    def Location(self):
        del self._location
        
    @property
    def Orientation(self):
        if hasattr(self, "_orientation"):
            return self._orientation
        else:
            return None
    
    @Orientation.setter    
    def Orientation(self, orientation):
        if orientation is None:
            if hasattr(self, "_orientation"):
                del self._orientation
        elif type(orientation) == Orientation:
            self._orientation = orientation
        else:
            raise TypeError("Orientation must be a kml.Orientation instance or None.")
    
    @Orientation.deleter
    def Orientation(self):
        del self._orientation
        
    @property
    def Link(self):
        if hasattr(self, "_link"):
            return self._link
        else:
            return None
        
    @Link.setter
    def Link(self, link):
        if link is None:
            if hasattr(self, "_link"):
                del self._link
        elif type(link) == Link:
            self._link = link
        elif type(link) == str:
            self._link = Link(href=link)
        else:
            raise TypeError("Link must be a kml.Link, string or None.")
        
    @Link.deleter
    def Link(self):
        del self._link

    @property
    def Scale(self):
        if hasattr(self, "_scale"):
            return self._scale
        else:
            return None
        
    @Scale.setter
    def Scale(self, scale):
        if scale is None:
            if hasattr(self, "_scale"):
                del self._scale
        elif type(scale) == Scale:
            self._scale = scale
        else:
            raise TypeError("Scale must be a kml.Scale instance or None.")
        
    @Scale.deleter
    def Scale(self):
        del self._scale
    
    @property
    def ResourceMap(self):
        if hasattr(self, "_resourceMap"):
            return self._resourceMap
        else:
            return None
    
    @ResourceMap.setter
    def ResourceMap(self, resourceMap):
        if resourceMap is None:
            if hasattr(self, "_resourceMap"):
                del self._resourceMap
        elif type(resourceMap) == ResourceMap:
            self._resourceMap = resourceMap
        else:
            raise TypeError("ResourceMap must be a kml.ResourceMap instance or None.")
    
    def toXmlNode(self, kml):
        node = Geometry.toXmlNode(self, kml)
        if self.altitudeMode:
            mode = altitudeMode(self.altitudeMode)
            node.appendChild(mode.toXmlNode(kml))
        if self.Location:
            node.appendChild(self.Location.toXmlNode(kml))
        if self.Orientation:
            node.appendChild(self.Orientation.toXmlNode(kml))
        if self.Scale:
            node.appendChild(self.Scale.toXmlNode(kml))
        if self.Link:
            node.appendChild(self.Link.toXmlNode(kml))
        if self.ResourceMap:
            node.appendChild(self.ResourceMap.toXmlNode(kml))
        return node

    def parseKMLNode(self, KMLNode):
        Geometry.parseKMLNode(self, KMLNode)
        elems = KMLNode.getElementsByTagName("altitudeMode")
        if elems:
            self.altitudeMode = str(altitudeMode(elems[0].childNodes[0].data))
        elems = KMLNode.getElementsByTagName("Location")
        if elems:
            self.Location = Location()
            self.Location.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("Orientation")
        if elems:
            self.Orientation = Orientation()
            self.Orientation.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("Scale")
        if elems:
            self.Scale = Scale()
            self.Scale.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("Link")
        if elems:
            self.Link = Link()
            self.Link.parseKMLNode(elems[0])
        elems = KMLNode.getElementsByTagName("ResourceMap")
        if elems:
            self.ResourceMap = ResourceMap()
            self.ResourceMap.parseKMLNode(elems[0])
    